/*
 * SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

// -A   adjust self-extracting exe
// -J   junk zipfile prefix (unzipsfx)

use crate::cli::ZipArgs;
use crate::utils::log::LogConfig;
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};

use crate::utils::common::caculate_ratio;
use anyhow::Result;

pub fn execute(args: &ZipArgs) -> Result<()> {
    log::info!("Starting adjust operation...");
    if args.extractor.adjust_sfx {
        log::info!("adjust self-extracting exe...");
        adjust_offsets(args)?;
    }
    if args.extractor.junk_sfx {
        log::info!("remove SFX prefix...");
        remove_sfx_prefix(args)?;
    }
    Ok(())
}

/// 修正自解压SFX包的ZIP偏移（模拟zip -A）
pub fn adjust_offsets(args: &ZipArgs) -> anyhow::Result<()> {
    let zip_path = args.zipfile.as_ref().unwrap();
    // 1. 打开文件，读取全部内容
    let mut file = OpenOptions::new().read(true).write(true).open(zip_path)?;
    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer)?;

    // 2. 查找第一个PK\x03\x04头（本地文件头）
    let mut zip_start = None;
    for i in 0..buffer.len() - 3 {
        if &buffer[i..i + 4] == b"PK\x03\x04" {
            zip_start = Some(i);
            break;
        }
    }
    let zip_start = zip_start.ok_or_else(|| anyhow::anyhow!("not found zip start"))?;
    log::debug!("zip start offset: {}", zip_start);

    // 3. 查找End of Central Directory（PK\x05\x06）
    let mut eocd_pos = None;
    for i in (0..buffer.len() - 3).rev() {
        if &buffer[i..i + 4] == b"PK\x05\x06" {
            eocd_pos = Some(i);
            break;
        }
    }
    let eocd_pos = eocd_pos.ok_or_else(|| anyhow::anyhow!("not found End of Central Directory"))?;
    log::debug!("eocd offset: {}", eocd_pos);

    // 4. 读取中央目录偏移和数量
    let cd_offset_pos = eocd_pos + 16;
    let cd_offset =
        u32::from_le_bytes(buffer[cd_offset_pos..cd_offset_pos + 4].try_into().unwrap());
    let cd_count = u16::from_le_bytes(buffer[eocd_pos + 10..eocd_pos + 12].try_into().unwrap());

    if !args.basic_options.quiet {
        // 检查是否已经修复过（中央目录偏移是否已经包含zip_start）
        if cd_offset >= zip_start as u32 {
            println!("Zip entry offsets do not need adjusting");
            log::info!("Zip entry offsets do not need adjusting");
            return Ok(());
        }
        println!(
            "Zip entry offsets appear off by {} bytes - correcting...",
            zip_start
        );
    }

    // 5. 修正中央目录中每个local_header_offset
    let mut cd_pos = zip_start + cd_offset as usize;
    let mut total_compressed_size = 0u64;
    let mut total_uncompressed_size = 0u64;

    for _ in 0..cd_count {
        // 检查中央目录头签名
        if &buffer[cd_pos..cd_pos + 4] != b"PK\x01\x02" {
            return Err(anyhow::anyhow!("Central directory header signature error"));
        }

        // 读取压缩大小(第20字节)和未压缩大小(第24字节)
        let compressed_size =
            u32::from_le_bytes(buffer[cd_pos + 20..cd_pos + 24].try_into().unwrap()) as u64;
        let uncompressed_size =
            u32::from_le_bytes(buffer[cd_pos + 24..cd_pos + 28].try_into().unwrap()) as u64;
        total_compressed_size += compressed_size;
        total_uncompressed_size += uncompressed_size;

        // local_header_offset字段在第42字节处
        let offset_pos = cd_pos + 42;
        let old_offset = u32::from_le_bytes(buffer[offset_pos..offset_pos + 4].try_into().unwrap());
        let new_offset = old_offset + zip_start as u32;
        buffer[offset_pos..offset_pos + 4].copy_from_slice(&new_offset.to_le_bytes());

        // 跳过中央目录头
        let filename_len =
            u16::from_le_bytes(buffer[cd_pos + 28..cd_pos + 30].try_into().unwrap()) as usize;
        let extra_len =
            u16::from_le_bytes(buffer[cd_pos + 30..cd_pos + 32].try_into().unwrap()) as usize;
        let comment_len =
            u16::from_le_bytes(buffer[cd_pos + 32..cd_pos + 34].try_into().unwrap()) as usize;
        cd_pos += 46 + filename_len + extra_len + comment_len;
    }

    // 6. 修正End of Central Directory中的中央目录偏移
    let new_cd_offset = cd_offset + zip_start as u32;
    buffer[cd_offset_pos..cd_offset_pos + 4].copy_from_slice(&new_cd_offset.to_le_bytes());

    // 7. 写回文件
    file.seek(SeekFrom::Start(0))?;
    file.write_all(&buffer)?;

    LogConfig::println_verbose(&format!(
        "total bytes={}, compressed={} -> {:.0}% savings",
        total_uncompressed_size,
        total_compressed_size,
        caculate_ratio(total_uncompressed_size, total_compressed_size),
    ));

    Ok(())
}

/// 移除自解压SFX前缀，保留纯ZIP数据（模拟zip -J）
pub fn remove_sfx_prefix(args: &ZipArgs) -> anyhow::Result<()> {
    use std::io::{BufReader, BufWriter};

    let zip_path = args.zipfile.as_ref().unwrap();
    let out_path = args.other.out.clone().unwrap_or_else(|| {
        let mut out_path = zip_path.clone();
        out_path.set_extension("zip");
        out_path
    });

    // 1. 查找ZIP数据起始位置
    let mut in_file = OpenOptions::new().read(true).open(zip_path)?;
    let mut reader = BufReader::new(&in_file);
    let mut zip_start = 0u64;
    let mut buf = [0u8; 4];
    loop {
        let n = reader.read(&mut buf)?;
        if n < 4 {
            return Err(anyhow::anyhow!("not found zip start"));
        }
        if &buf == b"PK\x03\x04" {
            break;
        }
        zip_start += 1;
        reader.seek(SeekFrom::Start(zip_start))?;
    }
    log::debug!("zip start offset: {}", zip_start);

    if zip_start == 0 {
        log::debug!("SFX prefix not found, no need to adjust central directory offset");
        return Ok(());
    }

    // 2. 查找EOCD（End of Central Directory）
    let file_len = in_file.metadata()?.len();
    let mut eocd_pos = None;
    let mut search_pos = file_len.saturating_sub(22); // EOCD最小长度
    let mut search_buf = vec![0u8; 1024 * 8];
    while search_pos > 0 {
        let read_len = std::cmp::min(search_buf.len() as u64, search_pos + 4);
        in_file.seek(SeekFrom::Start(search_pos - (read_len - 4)))?;
        in_file.read_exact(&mut search_buf[..read_len as usize])?;
        if let Some(pos) = search_buf[..read_len as usize]
            .windows(4)
            .rposition(|w| w == b"PK\x05\x06")
        {
            eocd_pos = Some(search_pos - (read_len - 4) + pos as u64);
            break;
        }
        if search_pos < search_buf.len() as u64 {
            break;
        }
        search_pos -= search_buf.len() as u64 - 4;
    }
    let eocd_pos = eocd_pos.ok_or_else(|| anyhow::anyhow!("not found End of Central Directory"))?;
    log::debug!("eocd offset: {}", eocd_pos);

    // 3. 读取EOCD注释长度，计算EOCD实际长度
    in_file.seek(SeekFrom::Start(eocd_pos + 20))?;
    let mut comment_len_bytes = [0u8; 2];
    in_file.read_exact(&mut comment_len_bytes)?;
    let comment_len = u16::from_le_bytes(comment_len_bytes) as u64;
    let eocd_len = 22 + comment_len;

    // 4. 读取中央目录偏移和数量
    in_file.seek(SeekFrom::Start(eocd_pos + 16))?;
    let mut cd_offset_bytes = [0u8; 4];
    in_file.read_exact(&mut cd_offset_bytes)?;
    let cd_offset = u32::from_le_bytes(cd_offset_bytes);

    in_file.seek(SeekFrom::Start(eocd_pos + 10))?;
    let mut cd_count_bytes = [0u8; 2];
    in_file.read_exact(&mut cd_count_bytes)?;
    let cd_count = u16::from_le_bytes(cd_count_bytes);

    // 5. 流式复制ZIP数据部分到输出文件（只复制到EOCD结尾）
    let mut in_file = OpenOptions::new().read(true).open(zip_path)?;
    in_file.seek(SeekFrom::Start(zip_start))?;
    let mut out_file = BufWriter::new(
        OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&out_path)?,
    );

    let copy_len = (eocd_pos + eocd_len) - zip_start;
    let mut bytes_left = copy_len;
    let mut buffer = vec![0u8; 1024 * 1024];
    while bytes_left > 0 {
        let read_size = std::cmp::min(bytes_left, buffer.len() as u64) as usize;
        let n = in_file.read(&mut buffer[..read_size])?;
        if n == 0 {
            break;
        }
        out_file.write_all(&buffer[..n])?;
        bytes_left -= n as u64;
    }
    out_file.flush()?;

    // 6. 修正输出文件中的中央目录每个local header offset
    let mut out_file = OpenOptions::new().read(true).write(true).open(&out_path)?;
    // 计算中央目录在新文件中的偏移
    let new_cd_offset = if cd_offset >= zip_start as u32 {
        cd_offset - zip_start as u32
    } else {
        cd_offset
    };
    let cd_start = new_cd_offset as u64;
    let mut cd_pos = cd_start;
    let mut total_compressed_size = 0u64;
    let mut total_uncompressed_size = 0u64;
    for _ in 0..cd_count {
        out_file.seek(SeekFrom::Start(cd_pos))?;
        let mut sig = [0u8; 4];
        out_file.read_exact(&mut sig)?;
        if &sig != b"PK\x01\x02" {
            return Err(anyhow::anyhow!("Central directory header signature error"));
        }
        // 读取压缩和未压缩大小
        out_file.seek(SeekFrom::Start(cd_pos + 20))?;
        let mut size_buf = [0u8; 8];
        out_file.read_exact(&mut size_buf)?;
        let compressed_size =
            u32::from_le_bytes([size_buf[0], size_buf[1], size_buf[2], size_buf[3]]) as u64;
        let uncompressed_size =
            u32::from_le_bytes([size_buf[4], size_buf[5], size_buf[6], size_buf[7]]) as u64;
        total_compressed_size += compressed_size;
        total_uncompressed_size += uncompressed_size;

        // local_header_offset字段在第42字节处
        out_file.seek(SeekFrom::Start(cd_pos + 42))?;
        let mut offset_bytes = [0u8; 4];
        out_file.read_exact(&mut offset_bytes)?;
        let old_offset = u32::from_le_bytes(offset_bytes);
        let new_offset = if old_offset >= zip_start as u32 {
            old_offset - zip_start as u32
        } else {
            old_offset
        };
        out_file.seek(SeekFrom::Start(cd_pos + 42))?;
        out_file.write_all(&new_offset.to_le_bytes())?;

        // 读取文件名长度、扩展字段长度、注释长度
        out_file.seek(SeekFrom::Start(cd_pos + 28))?;
        let mut lens = [0u8; 6];
        out_file.read_exact(&mut lens)?;
        let filename_len = u16::from_le_bytes([lens[0], lens[1]]) as u64;
        let extra_len = u16::from_le_bytes([lens[2], lens[3]]) as u64;
        let comment_len = u16::from_le_bytes([lens[4], lens[5]]) as u64;

        // 读取文件名并打印
        out_file.seek(SeekFrom::Start(cd_pos + 46))?;
        let mut name_buf = vec![0u8; filename_len as usize];
        out_file.read_exact(&mut name_buf)?;
        let filename = String::from_utf8_lossy(&name_buf);
        LogConfig::println(&format!(" copying: {}", filename));

        cd_pos += 46 + filename_len + extra_len + comment_len;
    }

    // 7. 修正输出文件中的EOCD中央目录偏移
    out_file.seek(SeekFrom::Start(eocd_pos - zip_start + 16))?;
    out_file.write_all(&new_cd_offset.to_le_bytes())?;

    LogConfig::println_verbose(&format!(
        "total bytes={}, compressed={} -> {:.0}% savings",
        total_uncompressed_size,
        total_compressed_size,
        caculate_ratio(total_uncompressed_size, total_compressed_size),
    ));

    Ok(())
}
